#include "Match.h"
#include "Player.h"
#include "TeamManager.h"
#include "ZStream.h"
#include "RandomSystem.h"
#include "GameSettings.h"
#include "ServiceProvider.h"

#include <cassert>

const float Match::INJURY_THRESHOLD = 0.05f;

Match::Match(ServiceProvider* i_sp) 
:m_sp(i_sp)
{
}

Match::Match(ServiceProvider* i_sp, Team* const i_t1, Team* const i_t2)
{
	assert(i_sp);
	m_sp = i_sp;

	m_minute = 0;

	int numberOfTitolars = Team::GetParams().TITOLARS_PLAYERS;
	
	m_data[0].m_goals = 0;
	m_data[0].m_team = i_t1;
	m_data[1].m_goals = 0;
	m_data[1].m_team = i_t2;

	m_data[0].m_teamStamina.resize(numberOfTitolars);
	m_data[0].m_teamAttackSkill.resize(numberOfTitolars);
	m_data[1].m_teamStamina.resize(numberOfTitolars);
	m_data[1].m_teamAttackSkill.resize(numberOfTitolars);

	InitData();

	for (int i = 0; i < numberOfTitolars; ++i)
	{
		m_data[0].m_teamAttackSkill[i] /= m_data[0].m_teamAttackSum;
		m_data[1].m_teamAttackSkill[i] /= m_data[1].m_teamAttackSum;
	}
}

void Match::InitData() 
{
	const size_t numberOfTitolars = Team::GetParams().TITOLARS_PLAYERS;

	for(size_t j=0; j<2; ++j)
	{
		const PlayerVec& playerList = m_data[j].m_team->GetPlayersList();
		
		m_data[j].m_teamAttackSum = 0.0f;
		m_data[j].m_teamDefenseSum = 0.0f;
		m_data[j].m_gkSkill= static_cast<float>(playerList.at(0)->GetSkill(Goalkeeper)) / 100.0f;

		for (size_t i = 0; i< numberOfTitolars; ++i)
		{
			const Player& thePlayer = *playerList.at(i);
			m_data[j].m_teamAttackSkill[i] = static_cast<float>(thePlayer.GetSkill(Attacker)) / 100.0f;
			m_data[j].m_teamDefenseSum += static_cast<float>(thePlayer.GetSkill(Defender)) / 100.0f;
			m_data[j].m_teamAttackSum += m_data[j].m_teamAttackSkill[i];
			m_data[j].m_teamStamina[i] = thePlayer.CalculateStamina();
		}
	}
}

std::string Match::CurrentResult()
{
	std::string result = "";
	result += Utilities::IntToString(m_data[0].m_goals) + " - " + Utilities::IntToString(m_data[1].m_goals);
	return result;
}

void Match::TeamsCondition(size_t i_teamIndex)
{
	const PlayerVec& playerList = m_data[i_teamIndex].m_team->GetPlayersList();
	PlayerVecCit end = playerList.begin() + Team::GetParams().TITOLARS_PLAYERS;
	for (PlayerVecCit it = playerList.begin(); it != end; ++it)
	{
		const Player& thePlayer = *(*it);
		if (thePlayer.IsBroken())
			AppendEvent("prematch", thePlayer.GetName());
	}
}

void Match::DecreaseStamina()
{
	const int numberOfGoalkeepers = Team::GetParams().TITOLARS_GOALKEEPERS;
	const int numberOfDefenders = Team::GetParams().TITOLARS_DEFENDERS;
	const int numberOfAttackers = Team::GetParams().TITOLARS_ATTACKERS;
	const float STAMINA_DECREASE = 0.005f;
	
	for(int j=0; j<2; ++j)
	{
		MatchTeamData& data = m_data[j];
		for (int i = 0; i < numberOfGoalkeepers; ++i)
		{
			data.m_teamStamina[i] = std::max(0.0f, data.m_teamStamina[i] - STAMINA_DECREASE * 0.5f);
		}

		for (int i = 0; i < numberOfDefenders; ++i)
		{
			data.m_teamStamina[numberOfGoalkeepers + i] = std::max(0.0f, data.m_teamStamina[numberOfGoalkeepers + i] - STAMINA_DECREASE);
		}

		for (int i = 0; i < numberOfAttackers; ++i)
		{
			const float t = data.m_teamStamina[numberOfDefenders + numberOfGoalkeepers + i];
			data.m_teamStamina[numberOfDefenders + numberOfGoalkeepers + i] = std::max(0.0f, t - STAMINA_DECREASE * 1.5f);
		}
	}
}

void Match::CheckInMatchInjury()
{
}

void Match::PostMatchPlayerUpdate(const bool i_produceEvents)
{
	// Skills Update, Fatugue Update, Injury Check, Healing of non playing players..

	const size_t numberOfTitolars = Team::GetParams().TITOLARS_PLAYERS;
	const size_t numberOfGK = Team::GetParams().TITOLARS_GOALKEEPERS;

	for(size_t j=0; j<2; ++j)
	{
		const PlayerVec& playerList = m_data[j].m_team->GetPlayersList();
		for(size_t i=0; i<playerList.size(); ++i)
		{
			Player& player = *playerList[i];
			player.PostMatchUpdateSkills(Team::GetRoleByIndex(i)); // Skills Update
			if(i < numberOfTitolars)
			{
				player.AddPlayedMatch();
				player.UpdateFatigue((i<numberOfGK) ? 1 : 2); // Fatugue Update
				if(player.InjuryTest(m_data[j].m_teamStamina[i] < INJURY_THRESHOLD)) // Injury Check
					if(i_produceEvents)
						AppendEvent("injuries", player.GetName());
			}
			else
				player.Heal(); // Healing
		}
	}
}

void Match::Shot(const size_t teamIndex, const bool i_produceEvents)
{
	int numberOfTitolars = Team::GetParams().TITOLARS_PLAYERS;

	float diceRoll = RandFloat();

	int shootingPlayerIndex = numberOfTitolars - 1;
	float min = 0.0f;

	int i = 0; 
	while (i < numberOfTitolars)
	{
		if (diceRoll < min + m_data[teamIndex].m_teamAttackSkill[i])
		{
			shootingPlayerIndex = i;
			break;
		}

		min += m_data[teamIndex].m_teamAttackSkill[i];
		++i;
	}
	assert(shootingPlayerIndex >= 0 && shootingPlayerIndex < numberOfTitolars && "Player out of index");
	Player* shootingPlayer = m_data[teamIndex].m_team->GetPlayersList().at(shootingPlayerIndex);
	if(i_produceEvents)
		AppendEvent("shots", shootingPlayer->GetName());

	const float k = m_data[teamIndex].m_teamAttackSkill[shootingPlayerIndex] * m_data[teamIndex].m_teamStamina[shootingPlayerIndex];

	diceRoll = RandFloat();
	if (diceRoll < std::max(0.5f, k)) // da rimodulare
	{
		float saveT = m_data[1 - teamIndex].m_gkSkill * m_data[1 - teamIndex].m_teamStamina[0];
		saveT /= saveT + k;

		diceRoll = RandFloat();
		if (diceRoll < saveT)
		{
			Player* goalkeeper = m_data[1 - teamIndex].m_team->GetPlayersList().at(0);
			if(i_produceEvents)
				AppendEvent("saves", goalkeeper->GetName());
		}
		else
		{
			++m_data[teamIndex].m_goals;
			if(i_produceEvents)
				AppendEvent("goals", CurrentResult());
			shootingPlayer->ScoreGoal();
		}
	}
	else if(i_produceEvents)
	{
		diceRoll = RandFloat();
		if (diceRoll <= 0.4f)
			AppendEvent("posts", "");
		else
			AppendEvent("outsides", "");
	}
}

void Match::Play(const bool i_produceEvents)
{
	m_minute = 0;
	
	//Condizioni pre-partita:
	if(i_produceEvents)
	{
		AppendEvent("weather", "");
		AppendEvent("location", "");
		TeamsCondition(0);
		TeamsCondition(1);
	}

	const int numberOfTitolars = Team::GetParams().TITOLARS_PLAYERS;
	const int matchDuration = m_sp->GetSettings().GetValue("I_MATCH_DURATION");
	
	//TO DO: move these in settings or elsewhere.
	const float shotThreshold = 0.5f;//*/0.05f;
	const float homeFactor = 0.5f;

	const float attackFactor1 = homeFactor + std::max(shotThreshold, m_data[0].m_teamAttackSum - m_data[1].m_teamDefenseSum * 0.5f);
	const float attackFactor2 = std::max(shotThreshold, m_data[1].m_teamAttackSum - m_data[0].m_teamDefenseSum * 0.5f);
	const float shotProbability = (attackFactor1 + attackFactor2) / (2.0f * numberOfTitolars);
	assert(shotProbability > 0.0f); //it is possible (but very very hard to reproduce) that shotProb > 1. It's ok, we don't mind... a lot of shots!

	while (m_minute < matchDuration) 
	{
		if(i_produceEvents && (m_minute == matchDuration / 2))
			AppendEvent("break", "");

		const float diceRoll = RandFloat();
		if (diceRoll <= shotProbability)
		{
			const float homeShotProbability = attackFactor1 / (attackFactor1 + attackFactor2);
			const float shotDiceRoll = RandFloat();
			Shot((shotDiceRoll < homeShotProbability) ? 0 : 1, i_produceEvents);
		}

		DecreaseStamina();
		//CheckInMatchInjury();

		++m_minute;
	}

	PostMatchPlayerUpdate(i_produceEvents);
	
	if(i_produceEvents)
		AppendEvent("postmatch", CurrentResult());

	return;
}

void Match::AppendEvent(const std::string& i_category, const std::string& i_name)
{
	m_events.push_back(Event(m_minute, m_sp->GetMatchEventGenerator().RandomEventMessageInCategory(i_category, i_name)));
}

#pragma region serializationAndStream

ZStream& Match::Serialize(ZStream& o_stream)
{
	o_stream.WriteOpenTag("Match");
	o_stream.WriteAttribute("Team1", m_data[0].m_team->GetID());
	o_stream.WriteAttribute("Team2", m_data[1].m_team->GetID());
	o_stream.WriteAttribute("Team1Goal", m_data[0].m_goals);
	o_stream.WriteAttribute("Team2Goal", m_data[1].m_goals);
	o_stream.WriteCloseTag("Match");
	return o_stream;
}

ZStream& Match::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();

	size_t team1Id, team2Id; 

	i_stream.ReadAttribute(team1Id);
	i_stream.ReadAttribute(team2Id);

	m_data[0].m_team = m_sp->GetTeamMgr().GetTeamFromID(team1Id);
	m_data[1].m_team = m_sp->GetTeamMgr().GetTeamFromID(team2Id);

	i_stream.ReadAttribute(m_data[0].m_goals);
	i_stream.ReadAttribute(m_data[1].m_goals);
	i_stream.SkipLine();
	return i_stream;
}

#pragma endregion